<!DOCTYPE html>
<html style="display: none;" lang="zh">
    <head>
    <meta charset="utf-8">
    <!--
        © Material Theme
        https://github.com/viosey/hexo-theme-material
        Version: 1.5.0 -->
    <script>
        window.materialVersion = "1.5.0"
        // Delete localstorage with these tags
        window.oldVersion = [
            'codestartv1',
            '1.3.4',
            '1.4.0',
            '1.4.0b1'
        ]
    </script>

    <!-- dns prefetch -->
    <meta http-equiv="x-dns-prefetch-control" content="on">



    <link rel="dns-prefetch" href="https://busuanzi.ibruce.info"/>












    <!-- Title -->
    
    <title>
        
            ASP.NET Core 中的SEO优化（1）：中间件实现服务端静态化缓存 | 
        
        ElderJames&#39; Blog
    </title>

    <!-- Meta & Info -->
    <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
    <meta name="format-detection" content="telephone=no"/>
    <meta name="theme-color" content="#0097A7">
    <meta name="author" content="James Yeung">
    <meta name="description" itemprop="description" content="学习、札记、分享">
    <meta name="keywords" content="ElderJames&#39; Blog,.NET Core,ASP.NET Core">

    <!-- Import lsloader -->
    <script>(function(){window.lsloader={jsRunSequence:[],jsnamemap:{},cssnamemap:{}};lsloader.removeLS=function(key){try{localStorage.removeItem(key)}catch(e){}};lsloader.setLS=function(key,val){try{localStorage.setItem(key,val)}catch(e){}};lsloader.getLS=function(key){var val="";try{val=localStorage.getItem(key)}catch(e){val=""}return val};versionString="/*"+(window.materialVersion||"unknownVersion")+"*/";lsloader.clean=function(){try{var keys=[];for(var i=0;i<localStorage.length;i++){keys.push(localStorage.key(i))}keys.forEach(function(key){var data=lsloader.getLS(key);if(window.oldVersion){var remove=window.oldVersion.reduce(function(p,c){return p||data.indexOf("/*"+c+"*/")!==-1},false);if(remove){lsloader.removeLS(key)}}})}catch(e){}};lsloader.clean();lsloader.load=function(jsname,jspath,cssonload,isJs){if(typeof cssonload==="boolean"){isJs=cssonload;cssonload=undefined}isJs=isJs||false;cssonload=cssonload||function(){};var code;code=this.getLS(jsname);if(code&&code.indexOf(versionString)===-1){this.removeLS(jsname);this.requestResource(jsname,jspath,cssonload,isJs);return}if(code){var versionNumber=code.split(versionString)[0];if(versionNumber!=jspath){console.log("reload:"+jspath);this.removeLS(jsname);this.requestResource(jsname,jspath,cssonload,isJs);return}code=code.split(versionString)[1];if(isJs){this.jsRunSequence.push({name:jsname,code:code});this.runjs(jspath,jsname,code)}else{document.getElementById(jsname).appendChild(document.createTextNode(code));cssonload()}}else{this.requestResource(jsname,jspath,cssonload,isJs)}};lsloader.requestResource=function(name,path,cssonload,isJs){var that=this;if(isJs){this.iojs(path,name,function(path,name,code){that.setLS(name,path+versionString+code);that.runjs(path,name,code)})}else{this.iocss(path,name,function(code){document.getElementById(name).appendChild(document.createTextNode(code));that.setLS(name,path+versionString+code)},cssonload)}};lsloader.iojs=function(path,jsname,callback){var that=this;that.jsRunSequence.push({name:jsname,code:""});try{var xhr=new XMLHttpRequest;xhr.open("get",path,true);xhr.onreadystatechange=function(){if(xhr.readyState==4){if(xhr.status>=200&&xhr.status<300||xhr.status==304){if(xhr.response!=""){callback(path,jsname,xhr.response);return}}that.jsfallback(path,jsname)}};xhr.send(null)}catch(e){that.jsfallback(path,jsname)}};lsloader.iocss=function(path,jsname,callback,cssonload){var that=this;try{var xhr=new XMLHttpRequest;xhr.open("get",path,true);xhr.onreadystatechange=function(){if(xhr.readyState==4){if(xhr.status>=200&&xhr.status<300||xhr.status==304){if(xhr.response!=""){callback(xhr.response);cssonload();return}}that.cssfallback(path,jsname,cssonload)}};xhr.send(null)}catch(e){that.cssfallback(path,jsname,cssonload)}};lsloader.iofonts=function(path,jsname,callback,cssonload){var that=this;try{var xhr=new XMLHttpRequest;xhr.open("get",path,true);xhr.onreadystatechange=function(){if(xhr.readyState==4){if(xhr.status>=200&&xhr.status<300||xhr.status==304){if(xhr.response!=""){callback(xhr.response);cssonload();return}}that.cssfallback(path,jsname,cssonload)}};xhr.send(null)}catch(e){that.cssfallback(path,jsname,cssonload)}};lsloader.runjs=function(path,name,code){if(!!name&&!!code){for(var k in this.jsRunSequence){if(this.jsRunSequence[k].name==name){this.jsRunSequence[k].code=code}}}if(!!this.jsRunSequence[0]&&!!this.jsRunSequence[0].code&&this.jsRunSequence[0].status!="failed"){var script=document.createElement("script");script.appendChild(document.createTextNode(this.jsRunSequence[0].code));script.type="text/javascript";document.getElementsByTagName("head")[0].appendChild(script);this.jsRunSequence.shift();if(this.jsRunSequence.length>0){this.runjs()}}else if(!!this.jsRunSequence[0]&&this.jsRunSequence[0].status=="failed"){var that=this;var script=document.createElement("script");script.src=this.jsRunSequence[0].path;script.type="text/javascript";this.jsRunSequence[0].status="loading";script.onload=function(){that.jsRunSequence.shift();if(that.jsRunSequence.length>0){that.runjs()}};document.body.appendChild(script)}};lsloader.tagLoad=function(path,name){this.jsRunSequence.push({name:name,code:"",path:path,status:"failed"});this.runjs()};lsloader.jsfallback=function(path,name){if(!!this.jsnamemap[name]){return}else{this.jsnamemap[name]=name}for(var k in this.jsRunSequence){if(this.jsRunSequence[k].name==name){this.jsRunSequence[k].code="";this.jsRunSequence[k].status="failed";this.jsRunSequence[k].path=path}}this.runjs()};lsloader.cssfallback=function(path,name,cssonload){if(!!this.cssnamemap[name]){return}else{this.cssnamemap[name]=1}var link=document.createElement("link");link.type="text/css";link.href=path;link.rel="stylesheet";link.onload=link.onerror=cssonload;var root=document.getElementsByTagName("script")[0];root.parentNode.insertBefore(link,root)};lsloader.runInlineScript=function(scriptId,codeId){var code=document.getElementById(codeId).innerText;this.jsRunSequence.push({name:scriptId,code:code});this.runjs()};lsloader.loadCombo=function(jslist){var updateList="";var requestingModules={};for(var k in jslist){var LS=this.getLS(jslist[k].name);if(!!LS){var version=LS.split(versionString)[0];var code=LS.split(versionString)[1]}else{var version=""}if(version==jslist[k].path){this.jsRunSequence.push({name:jslist[k].name,code:code,path:jslist[k].path})}else{this.jsRunSequence.push({name:jslist[k].name,code:null,path:jslist[k].path,status:"comboloading"});requestingModules[jslist[k].name]=true;updateList+=(updateList==""?"":";")+jslist[k].path}}var that=this;if(!!updateList){var xhr=new XMLHttpRequest;xhr.open("get",combo+updateList,true);xhr.onreadystatechange=function(){if(xhr.readyState==4){if(xhr.status>=200&&xhr.status<300||xhr.status==304){if(xhr.response!=""){that.runCombo(xhr.response,requestingModules);return}}else{for(var i in that.jsRunSequence){if(requestingModules[that.jsRunSequence[i].name]){that.jsRunSequence[i].status="failed"}}that.runjs()}}};xhr.send(null)}this.runjs()};lsloader.runCombo=function(comboCode,requestingModules){comboCode=comboCode.split("/*combojs*/");comboCode.shift();for(var k in this.jsRunSequence){if(!!requestingModules[this.jsRunSequence[k].name]&&!!comboCode[0]){this.jsRunSequence[k].status="comboJS";this.jsRunSequence[k].code=comboCode[0];this.setLS(this.jsRunSequence[k].name,this.jsRunSequence[k].path+versionString+comboCode[0]);comboCode.shift()}}this.runjs()}})();</script>

    <!-- Import queue -->
    <script>function Queue(){this.dataStore=[];this.offer=b;this.poll=d;this.execNext=a;this.debug=false;this.startDebug=c;function b(e){if(this.debug){console.log("Offered a Queued Function.")}if(typeof e==="function"){this.dataStore.push(e)}else{console.log("You must offer a function.")}}function d(){if(this.debug){console.log("Polled a Queued Function.")}return this.dataStore.shift()}function a(){var e=this.poll();if(e!==undefined){if(this.debug){console.log("Run a Queued Function.")}e()}}function c(){this.debug=true}}var queue=new Queue();</script>

    <!-- Favicons -->
    <link rel="icon shortcut" type="image/ico" href="/img/favicon.png">
    <link rel="icon" sizes="192x192" href="/img/favicon.png">
    <link rel="apple-touch-icon" href="/img/favicon.png">

    <!--iOS -->
    <meta name="apple-mobile-web-app-title" content="Title">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <meta name="HandheldFriendly" content="True">
    <meta name="MobileOptimized" content="480">

    <!-- Add to homescreen for Chrome on Android -->
    <meta name="mobile-web-app-capable" content="yes">

    <!-- Add to homescreen for Safari on iOS -->
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <meta name="apple-mobile-web-app-title" content="ElderJames&#39; Blog">

    <!-- Site Verification -->
    
    

    <!-- RSS -->
    
        
            <link rel=alternate type="application/rss+xml" href="rss.xml">
        
    

    <!--[if lte IE 9]>
        <link rel="stylesheet" href="/css/ie-blocker.css">

        
            <script src="/js/ie-blocker.zhCN.js"></script>
        
    <![endif]-->

    <!-- Import CSS -->
    
        <style id="material_css"></style><script>if(typeof window.lsLoadCSSMaxNums === "undefined")window.lsLoadCSSMaxNums = 0;window.lsLoadCSSMaxNums++;lsloader.load("material_css","/css/material.min.css?976y5g1pRWbrdcUItimr0g==",function(){if(typeof window.lsLoadCSSNums === "undefined")window.lsLoadCSSNums = 0;window.lsLoadCSSNums++;if(window.lsLoadCSSNums == window.lsLoadCSSMaxNums)document.documentElement.style.display="";}, false)</script>
        <style id="style_css"></style><script>if(typeof window.lsLoadCSSMaxNums === "undefined")window.lsLoadCSSMaxNums = 0;window.lsLoadCSSMaxNums++;lsloader.load("style_css","/css/style.min.css?J9YGUOg9VFzvsUAJ8qxFsQ==",function(){if(typeof window.lsLoadCSSNums === "undefined")window.lsLoadCSSNums = 0;window.lsLoadCSSNums++;if(window.lsLoadCSSNums == window.lsLoadCSSMaxNums)document.documentElement.style.display="";}, false)</script>

        
            
                <style id="prettify_css"></style><script>if(typeof window.lsLoadCSSMaxNums === "undefined")window.lsLoadCSSMaxNums = 0;window.lsLoadCSSMaxNums++;lsloader.load("prettify_css","/css/prettify.min.css?UDVrysExI1W3AEkuJF/bUQ==",function(){if(typeof window.lsLoadCSSNums === "undefined")window.lsLoadCSSNums = 0;window.lsLoadCSSNums++;if(window.lsLoadCSSNums == window.lsLoadCSSMaxNums)document.documentElement.style.display="";}, false)</script>
                <style id="prettify_theme"></style><script>if(typeof window.lsLoadCSSMaxNums === "undefined")window.lsLoadCSSMaxNums = 0;window.lsLoadCSSMaxNums++;lsloader.load("prettify_theme","/css/prettify/github-v2.min.css?AfzKxt++K+/lhZBlSjnxwg==",function(){if(typeof window.lsLoadCSSNums === "undefined")window.lsLoadCSSNums = 0;window.lsLoadCSSNums++;if(window.lsLoadCSSNums == window.lsLoadCSSMaxNums)document.documentElement.style.display="";}, false)</script>
            
        

    

    

    <!-- Config CSS -->

<!-- Other Styles -->
<style>
  body, html {
    font-family: Roboto, "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
    overflow-x: hidden !important;
  }
  
  code {
    font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
  }

  a {
    color: #00838F;
  }

  .mdl-card__media,
  #search-label,
  #search-form-label:after,
  #scheme-Paradox .hot_tags-count,
  #scheme-Paradox .sidebar_archives-count,
  #scheme-Paradox .sidebar-colored .sidebar-header,
  #scheme-Paradox .sidebar-colored .sidebar-badge{
    background-color: #0097A7 !important;
  }

  /* Sidebar User Drop Down Menu Text Color */
  #scheme-Paradox .sidebar-colored .sidebar-nav>.dropdown>.dropdown-menu>li>a:hover,
  #scheme-Paradox .sidebar-colored .sidebar-nav>.dropdown>.dropdown-menu>li>a:focus {
    color: #0097A7 !important;
  }

  #post_entry-right-info,
  .sidebar-colored .sidebar-nav li:hover > a,
  .sidebar-colored .sidebar-nav li:hover > a i,
  .sidebar-colored .sidebar-nav li > a:hover,
  .sidebar-colored .sidebar-nav li > a:hover i,
  .sidebar-colored .sidebar-nav li > a:focus i,
  .sidebar-colored .sidebar-nav > .open > a,
  .sidebar-colored .sidebar-nav > .open > a:hover,
  .sidebar-colored .sidebar-nav > .open > a:focus,
  #ds-reset #ds-ctx .ds-ctx-entry .ds-ctx-head a {
    color: #0097A7 !important;
  }

  .toTop {
    background: #757575 !important;
  }

  .material-layout .material-post>.material-nav,
  .material-layout .material-index>.material-nav,
  .material-nav a {
    color: #757575;
  }

  #scheme-Paradox .MD-burger-layer {
    background-color: #757575;
  }

  #scheme-Paradox #post-toc-trigger-btn {
    color: #757575;
  }

  .post-toc a:hover {
    color: #00838F;
    text-decoration: underline;
  }

</style>


<!-- Theme Background Related-->

    <style>
      body {
        background-color: undefined;
      }

      /* blog_info bottom background */
      #scheme-Paradox .material-layout .something-else .mdl-card__supporting-text {
        background-color: #fff;
      }
    </style>




<!-- Fade Effect -->

    <style>
      .fade {
        transition: all 800ms linear;
        -webkit-transform: translate3d(0,0,0);
        -moz-transform: translate3d(0,0,0);
        -ms-transform: translate3d(0,0,0);
        -o-transform: translate3d(0,0,0);
        transform: translate3d(0,0,0);
        opacity: 1;
      }

      .fade.out{
        opacity: 0;
      }
    </style>


<!-- Import Font -->
<!-- Import Roboto -->

    <style>
        @font-face {
            font-family: Roboto;
            font-style: normal;
            font-weight: 300;
            src: url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-300.eot);
            src: local('Roboto'),local('Roboto-Normal'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-300.eot?#iefix) format('embedded-opentype'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-300.woff2) format('woff2'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-300.woff) format('woff'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-300.ttf) format('truetype'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-300.svg#Roboto) format('svg')
        }

        @font-face {
            font-family: Roboto;
            font-style: normal;
            font-weight: regular;
            src: url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-regular.eot);
            src: local('Roboto'),local('Roboto-Normal'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-regular.eot?#iefix) format('embedded-opentype'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-regular.woff2) format('woff2'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-regular.woff) format('woff'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-regular.ttf) format('truetype'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-regular.svg#Roboto) format('svg')
        }

        @font-face {
            font-family: Roboto;
            font-style: normal;
            font-weight: 500;
            src: url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-500.eot);
            src: local('Roboto'),local('Roboto-Normal'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-500.eot?#iefix) format('embedded-opentype'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-500.woff2) format('woff2'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-500.woff) format('woff'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-500.ttf) format('truetype'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-500.svg#Roboto) format('svg')
        }
    </style>


<!-- Import Material Icon -->

    <link rel="stylesheet" href="//cdn.bootcss.com/material-design-icons/3.0.1/iconfont/material-icons.min.css">




    <!-- Import jQuery -->
    
        <script>lsloader.load("jq_js","https://cdn.bootcss.com/jquery/2.2.1/jquery.js", true)</script>
    

    <!-- The Open Graph protocol -->
    <meta property="og:url" content="https://yangshunjie.com">
    <meta property="og:type" content="blog">
    <meta property="og:title" content="ASP.NET Core 中的SEO优化（1）：中间件实现服务端静态化缓存 | ElderJames&#39; Blog">
    <meta property="og:image" content="https://yangshunjie.com/img/favicon.png" />
    <meta property="og:description" content="学习、札记、分享">
    <meta property="og:article:tag" content=".NET Core"> <meta property="og:article:tag" content="ASP.NET Core"> 

    
        <meta property="article:published_time" content="Thu Nov 23 2017 18:31:34 GMT+0000" />
        <meta property="article:modified_time" content="Fri Dec 15 2017 09:43:03 GMT+0000" />
    

    <!-- The Twitter Card protocol -->
    <meta name="twitter:title" content="ASP.NET Core 中的SEO优化（1）：中间件实现服务端静态化缓存 | ElderJames&#39; Blog">
    <meta name="twitter:description" content="学习、札记、分享">
    <meta name="twitter:image" content="https://yangshunjie.com/img/favicon.png">
    <meta name="twitter:card" content="summary_large_image" />
    <meta name="twitter:url" content="https://yangshunjie.com" />

    <!-- Add canonical link for SEO -->
    
        <link rel="canonical" href="https://yangshunjie.com/A-Middleware-Implement-For-Server-Side-Static-Caching-In-AspNetCore.html" />
    

    <!-- Structured-data for SEO -->
    
        


<script type="application/ld+json">
{
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "mainEntityOfPage": "https://yangshunjie.com/A-Middleware-Implement-For-Server-Side-Static-Caching-In-AspNetCore.html",
    "headline": "ASP.NET Core 中的SEO优化（1）：中间件实现服务端静态化缓存",
    "datePublished": "Thu Nov 23 2017 18:31:34 GMT+0000",
    "dateModified": "Fri Dec 15 2017 09:43:03 GMT+0000",
    "author": {
        "@type": "Person",
        "name": "James Yeung",
        "image": {
            "@type": "ImageObject",
            "url": "https://avatars0.githubusercontent.com/u/7550366?v=4&amp;s=460"
        },
        "description": "学习/创作/分享"
    },
    "publisher": {
        "@type": "Organization",
        "name": "ElderJames&#39; Blog",
        "logo": {
            "@type":"ImageObject",
            "url": "/img/favicon.png"
        }
    },
    "keywords": ",.NET Core,ASP.NET CoreElderJames&#39; Blog",
    "description": "学习、札记、分享",
}
</script>


    

    <!-- Analytics -->
    
    
    

    <!-- Custom Head -->
    

<link rel="stylesheet" href="/css/prism-vs.css" type="text/css">
<link rel="stylesheet" href="/css/prism-line-numbers.css" type="text/css"></head>


    
        <body id="scheme-Paradox" class="lazy">
            <div class="material-layout  mdl-js-layout has-drawer is-upgraded">
                

                <!-- Main Container -->
                <main class="material-layout__content" id="main">

                    <!-- Top Anchor -->
                    <div id="top"></div>

                    
                        <!-- Hamburger Button -->
                        <button class="MD-burger-icon sidebar-toggle">
                            <span class="MD-burger-layer"></span>
                        </button>
                    

                    <!-- Post TOC -->

    
    <!-- Back Button -->
    <!--
    <div class="material-back" id="backhome-div" tabindex="0">
        <a class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--icon"
           href="#" onclick="window.history.back();return false;"
           target="_self"
           role="button"
           data-upgraded=",MaterialButton,MaterialRipple">
            <i class="material-icons" role="presentation">arrow_back</i>
            <span class="mdl-button__ripple-container">
                <span class="mdl-ripple"></span>
            </span>
        </a>
    </div>
    -->


    <!-- Left aligned menu below button -->
    
    
    <button id="post-toc-trigger-btn"
        class="mdl-button mdl-js-button mdl-button--icon">
        <i class="material-icons">format_list_numbered</i>
    </button>

    <ul class="post-toc-wrap mdl-menu mdl-menu--bottom-left mdl-js-menu mdl-js-ripple-effect" for="post-toc-trigger-btn" style="max-height:80vh; overflow-y:scroll;">
        <ol class="post-toc"><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#分享"><span class="post-toc-number">1.</span> <span class="post-toc-text"><a href="#&#x5206;&#x4EAB;" class="headerlink" title="&#x5206;&#x4EAB;"></a>&#x5206;&#x4EAB;</span></a></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#背景"><span class="post-toc-number">2.</span> <span class="post-toc-text"><a href="#&#x80CC;&#x666F;" class="headerlink" title="&#x80CC;&#x666F;"></a>&#x80CC;&#x666F;</span></a></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#原理"><span class="post-toc-number">3.</span> <span class="post-toc-text"><a href="#&#x539F;&#x7406;" class="headerlink" title="&#x539F;&#x7406;"></a>&#x539F;&#x7406;</span></a></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#实现服务端静态化缓存中间件"><span class="post-toc-number">4.</span> <span class="post-toc-text"><a href="#&#x5B9E;&#x73B0;&#x670D;&#x52A1;&#x7AEF;&#x9759;&#x6001;&#x5316;&#x7F13;&#x5B58;&#x4E2D;&#x95F4;&#x4EF6;" class="headerlink" title="&#x5B9E;&#x73B0;&#x670D;&#x52A1;&#x7AEF;&#x9759;&#x6001;&#x5316;&#x7F13;&#x5B58;&#x4E2D;&#x95F4;&#x4EF6;"></a>&#x5B9E;&#x73B0;&#x670D;&#x52A1;&#x7AEF;&#x9759;&#x6001;&#x5316;&#x7F13;&#x5B58;&#x4E2D;&#x95F4;&#x4EF6;</span></a></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#反思"><span class="post-toc-number">5.</span> <span class="post-toc-text"><a href="#&#x53CD;&#x601D;" class="headerlink" title="&#x53CD;&#x601D;"></a>&#x53CD;&#x601D;</span></a></li></ol>
    </ul>
    




<!-- Layouts -->

    <!-- Post Module -->
    <div class="material-post_container">

        <div class="material-post mdl-grid">
            <div class="mdl-card mdl-shadow--4dp mdl-cell mdl-cell--12-col">

                <!-- Post Header(Thumbnail & Title) -->
                
    <!-- Paradox Post Header -->
    
        
            <!-- Random Thumbnail -->
            <div class="post_thumbnail-random mdl-card__media mdl-color-text--grey-50">
            <script type="text/ls-javascript" id="post-thumbnail-script">
    var randomNum = Math.floor(Math.random() * 19 + 1);

    $('.post_thumbnail-random').attr('data-original', '/img/random/material-' + randomNum + '.png');
    $('.post_thumbnail-random').addClass('lazy');
</script>

        
    
            <p class="article-headline-p">
                ASP.NET Core 中的SEO优化（1）：中间件实现服务端静态化缓存
            </p>
        </div>





                
                    <!-- Paradox Post Info -->
                    <div class="mdl-color-text--grey-700 mdl-card__supporting-text meta">

    <!-- Author Avatar -->
    <div id="author-avatar">
        <img src="https://avatars0.githubusercontent.com/u/7550366?v=4&s=460" width="44px" height="44px" alt="Author Avatar"/>
    </div>
    <!-- Author Name & Date -->
    <div>
        <strong>James Yeung</strong>
        <span>11月 23, 2017</span>
    </div>

    <div class="section-spacer"></div>

    <!-- Favorite -->
    <!--
        <button id="article-functions-like-button" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--icon btn-like">
            <i class="material-icons" role="presentation">favorite</i>
            <span class="visuallyhidden">favorites</span>
        </button>
    -->

    <!-- Qrcode -->
    
        <button id="article-functions-qrcode-button" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--icon">
    <i class="material-icons" role="presentation">devices other</i>
    <span class="visuallyhidden">devices other</span>
</button>
<ul class="mdl-menu mdl-menu--bottom-right mdl-js-menu mdl-js-ripple-effect" for="article-functions-qrcode-button">
    <li class="mdl-menu__item">在其它设备中阅读本文章</li>
    
        <img src="">
    
</ul>

    

    <!-- Tags (bookmark) -->
    
    <button id="article-functions-viewtags-button" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--icon">
        <i class="material-icons" role="presentation">bookmark</i>
        <span class="visuallyhidden">bookmark</span>
    </button>
    <ul class="mdl-menu mdl-menu--bottom-right mdl-js-menu mdl-js-ripple-effect" for="article-functions-viewtags-button">
        <li class="mdl-menu__item">
        <a class="post_tag-link" href="/tags/NET-Core/">.NET Core</a></li><li class="mdl-menu__item"><a class="post_tag-link" href="/tags/ASP-NET-Core/">ASP.NET Core</a>
    </ul>
    

    <!-- Share -->
    <button id="article-fuctions-share-button" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--icon">
    <i class="material-icons" role="presentation">share</i>
    <span class="visuallyhidden">share</span>
</button>
<ul class="mdl-menu mdl-menu--bottom-right mdl-js-menu mdl-js-ripple-effect" for="article-fuctions-share-button">
    

    
        
            <!-- Busuanzi Views -->
            <a class="post_share-link" href="#">
                <li class="mdl-menu__item">
                    <span id="busuanzi_container_page_pv">
                        <span id="busuanzi_value_page_pv"></span>&nbsp;浏览量
                    </span>
                </li>
            </a>
        
    

    <!-- Share Weibo -->
    
        <a class="post_share-link" href="http://service.weibo.com/share/share.php?appkey=&title=ASP.NET Core 中的SEO优化（1）：中间件实现服务端静态化缓存&url=https://yangshunjie.com/A-Middleware-Implement-For-Server-Side-Static-Caching-In-AspNetCore.html&pic=https://yangshunjie.com/img/favicon.png&searchPic=false&style=simple" target="_blank">
            <li class="mdl-menu__item">
                分享到微博
            </li>
        </a>
    

    <!-- Share Twitter -->
    

    <!-- Share Facebook -->
    

    <!-- Share Google+ -->
    

    <!-- Share LinkedIn -->
    

    <!-- Share QQ -->
    
        <a class="post_share-link" href="http://connect.qq.com/widget/shareqq/index.html?site=ElderJames&#39; Blog&title=ASP.NET Core 中的SEO优化（1）：中间件实现服务端静态化缓存&summary=学习、札记、分享&pics=https://yangshunjie.com/img/favicon.png&url=https://yangshunjie.com/A-Middleware-Implement-For-Server-Side-Static-Caching-In-AspNetCore.html" target="_blank">
            <li class="mdl-menu__item">
                分享到 QQ
            </li>
        </a>
    

    <!-- Share Telegram -->
    
</ul>

</div>

                

                <!-- Post Content -->
                <div id="post-content" class="mdl-color-text--grey-700 mdl-card__supporting-text fade out">
    
        <script src="\assets\js\APlayer.min.js"> </script><h2 id="分享"><a href="#分享" class="headerlink" title="分享"></a>分享</h2><p>最近在公司成功落地了一个用ASP.NET Core 开发前台的CMS项目，虽然对于表层的开发是兼容MVC5的，但是作为爱好者当然要用尽量多的ASP.NET Core新功能了。</p>
<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>在项目开发的过程中，为了满足需求，还是有许多功能要自己“发明”，也就是已有技术的组(qi)合ji)运(yin)用(qiao)。本例先讲讲如果用中间件开发所有CMS都需要的服务端静态缓存方法。</p>
<p>CMS系统的一大痛点是一个页面要查询的内容很多，所以常常为了减轻服务器压力，都会使用到各种缓存技术。比如静态文件的CDN缓存、客户端缓存、还有就是服务端静态化缓存。</p>
<p>服务端静态化缓存在这里指的是把页面事先生成出来保存为静态文件，当用户请求服务器时，可以直接把页面输出给用户，而不再进行查询数据库之类的操作，已达到提高响应速度、减轻服务器压力的效果。</p>
<h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><p>由于服务端静态化缓存的实现核心是需要拦截请求并且直接返回HTML内容，在MVC5时代，我们可以用过滤器（Filter）来处理，类似如下代码：</p>
<pre class="line-numbers language-csharp"><code class="language-csharp">    <span class="token punctuation">[</span><span class="token function">AttributeUsage</span><span class="token punctuation">(</span>AttributeTargets<span class="token punctuation">.</span>Class <span class="token operator">|</span> AttributeTargets<span class="token punctuation">.</span>Method<span class="token punctuation">,</span> AllowMultiple <span class="token operator">=</span> <span class="token keyword">false</span><span class="token punctuation">,</span> Inherited <span class="token operator">=</span> <span class="token keyword">false</span><span class="token punctuation">)</span><span class="token punctuation">]</span>
    <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">StaticFileHandlerFilterAttribute</span> <span class="token punctuation">:</span> ActionFilterAttribute
    <span class="token punctuation">{</span>
        <span class="token comment" spellcheck="true">/// &lt;summary></span>
        <span class="token comment" spellcheck="true">/// 过期时间，以小时为单位</span>
        <span class="token comment" spellcheck="true">/// &lt;/summary></span>
        <span class="token keyword">public</span> <span class="token keyword">int</span> Expiration <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>

        <span class="token keyword">public</span> <span class="token keyword">override</span> <span class="token keyword">void</span> <span class="token function">OnResultExecuted</span><span class="token punctuation">(</span>ResultExecutedContext filterContext<span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            <span class="token keyword">var</span> actionResult <span class="token operator">=</span> filterContext<span class="token punctuation">.</span>Result<span class="token punctuation">;</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>actionResult <span class="token keyword">is</span> ViewResult<span class="token punctuation">)</span>
            <span class="token punctuation">{</span>
                <span class="token keyword">var</span> fileInfo <span class="token operator">=</span> <span class="token function">GetFileInfo</span><span class="token punctuation">(</span>filterContext<span class="token punctuation">)</span><span class="token punctuation">;</span>

                <span class="token keyword">if</span> <span class="token punctuation">(</span>fileInfo<span class="token punctuation">.</span>Exists <span class="token operator">&amp;&amp;</span> fileInfo<span class="token punctuation">.</span>CreationTime<span class="token punctuation">.</span><span class="token function">AddHours</span><span class="token punctuation">(</span>Expiration <span class="token operator">&lt;=</span> <span class="token number">0</span> <span class="token operator">?</span> <span class="token number">1</span> <span class="token punctuation">:</span> Expiration<span class="token punctuation">)</span> <span class="token operator">></span> DateTime<span class="token punctuation">.</span>Now<span class="token punctuation">)</span>
                    <span class="token keyword">return</span><span class="token punctuation">;</span>

                <span class="token keyword">if</span> <span class="token punctuation">(</span>fileInfo<span class="token punctuation">.</span>Exists<span class="token punctuation">)</span>
                <span class="token punctuation">{</span>
                    fileInfo<span class="token punctuation">.</span><span class="token function">Delete</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span>

                <span class="token keyword">using</span> <span class="token punctuation">(</span>FileStream fs <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FileStream</span><span class="token punctuation">(</span>fileInfo<span class="token punctuation">.</span>FullName<span class="token punctuation">,</span> FileMode<span class="token punctuation">.</span>CreateNew<span class="token punctuation">,</span> FileAccess<span class="token punctuation">.</span>Write<span class="token punctuation">,</span> FileShare<span class="token punctuation">.</span>None<span class="token punctuation">)</span><span class="token punctuation">)</span>
                <span class="token punctuation">{</span>
                    <span class="token keyword">using</span> <span class="token punctuation">(</span>StreamWriter sw <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StreamWriter</span><span class="token punctuation">(</span>fs<span class="token punctuation">,</span> Encoding<span class="token punctuation">.</span>UTF8<span class="token punctuation">)</span><span class="token punctuation">)</span>
                    <span class="token punctuation">{</span>
                        <span class="token keyword">var</span> viewResult <span class="token operator">=</span> actionResult <span class="token keyword">as</span> ViewResult<span class="token punctuation">;</span>
                        <span class="token keyword">var</span> viewContext <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ViewContext</span><span class="token punctuation">(</span>filterContext<span class="token punctuation">.</span>Controller<span class="token punctuation">.</span>ControllerContext<span class="token punctuation">,</span> viewResult<span class="token punctuation">.</span>View<span class="token punctuation">,</span> viewResult<span class="token punctuation">.</span>ViewData<span class="token punctuation">,</span> viewResult<span class="token punctuation">.</span>TempData<span class="token punctuation">,</span> sw<span class="token punctuation">)</span><span class="token punctuation">;</span>
                        viewResult<span class="token punctuation">.</span>View<span class="token punctuation">.</span><span class="token function">Render</span><span class="token punctuation">(</span>viewContext<span class="token punctuation">,</span> sw<span class="token punctuation">)</span><span class="token punctuation">;</span>
                    <span class="token punctuation">}</span>
                <span class="token punctuation">}</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>

        <span class="token keyword">public</span> <span class="token keyword">override</span> <span class="token keyword">void</span> <span class="token function">OnActionExecuting</span><span class="token punctuation">(</span>ActionExecutingContext filterContext<span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            <span class="token keyword">var</span> fileInfo <span class="token operator">=</span> <span class="token function">GetFileInfo</span><span class="token punctuation">(</span>filterContext<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>fileInfo<span class="token punctuation">.</span>Exists<span class="token punctuation">)</span>
            <span class="token punctuation">{</span>
                <span class="token keyword">using</span> <span class="token punctuation">(</span>FileStream fs <span class="token operator">=</span> File<span class="token punctuation">.</span><span class="token function">Open</span><span class="token punctuation">(</span>fileInfo<span class="token punctuation">.</span>FullName<span class="token punctuation">,</span> FileMode<span class="token punctuation">.</span>Open<span class="token punctuation">)</span><span class="token punctuation">)</span>
                <span class="token punctuation">{</span>
                    <span class="token keyword">using</span> <span class="token punctuation">(</span>StreamReader sr <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StreamReader</span><span class="token punctuation">(</span>fs<span class="token punctuation">,</span> Encoding<span class="token punctuation">.</span>UTF8<span class="token punctuation">)</span><span class="token punctuation">)</span>
                    <span class="token punctuation">{</span>
                        ContentResult contentresult <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ContentResult</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                        contentresult<span class="token punctuation">.</span>Content <span class="token operator">=</span> sr<span class="token punctuation">.</span><span class="token function">ReadToEnd</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                        contentresult<span class="token punctuation">.</span>ContentType <span class="token operator">=</span> <span class="token string">"text/html"</span><span class="token punctuation">;</span>
                        filterContext<span class="token punctuation">.</span>Result <span class="token operator">=</span> contentresult<span class="token punctuation">;</span>
                    <span class="token punctuation">}</span>
                <span class="token punctuation">}</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>而在ASP.NET Core中，我们也可以在Filte中实现，但是，更方便、更牛、更快的方式就是在中间件中实现了。</p>
<p><img src="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/_static/request-delegate-pipeline.png" alt="中间件示意图"></p>
<p>中间件运行在请求管道中，个人理解是可以看成是一个递归方法，只不过是调用不同的中间件，中间件中调用下一个中间件、知道最后一个执行完成，会沿路返回上一个中间件……说得可能不清楚，但是看代码和调试一下就能明白了。下面用代码层错误拦截的中间件作为一个中间件生命周期的示例：</p>
<pre class="line-numbers language-csharp"><code class="language-csharp">    app<span class="token punctuation">.</span><span class="token function">Use</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span>context<span class="token punctuation">,</span> next<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">></span>
    <span class="token punctuation">{</span>
        <span class="token keyword">try</span>
        <span class="token punctuation">{</span>
            <span class="token comment" spellcheck="true">//执行下一个中间件前执行的代码</span>
            <span class="token keyword">await</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token comment" spellcheck="true">//上一个中间件执行后执行</span>
        <span class="token punctuation">}</span>
        <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> ex<span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            context<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>StatusCode <span class="token operator">=</span> <span class="token number">500</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token comment" spellcheck="true">//执行完后会继续执行上一个中间件next委托之后的语句</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>中间件是写在Startup.cs文件的<code>Configure</code>方法中，以<code>app.Use()</code>方法的执行顺序添加中间件，然后会从上往下运行，遇到<code>next</code>委托就会跳入下一个中间件，不执行<code>next</code>委托就会返回上一个中间件。</p>
<p>好了，关于中间件的具体介绍可以看<a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware?tabs=aspnetcore2x" target="_blank" rel="noopener">官方文档</a></p>
<h2 id="实现服务端静态化缓存中间件"><a href="#实现服务端静态化缓存中间件" class="headerlink" title="实现服务端静态化缓存中间件"></a>实现服务端静态化缓存中间件</h2><p>下面介绍思路，实现静态化缓存的核心是需要<strong>从请求响应中获取内容，并且转换为字符串保存到文件中</strong>。</p>
<p>这个功能点用以下代码和注释进行讲解：</p>
<pre class="line-numbers language-csharp"><code class="language-csharp"><span class="token comment" spellcheck="true">//获取响应体的引用</span>
<span class="token keyword">var</span> originalBody <span class="token operator">=</span> context<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>Body<span class="token punctuation">;</span>

<span class="token keyword">try</span>
<span class="token punctuation">{</span>
    <span class="token comment" spellcheck="true">//因为响应体实例是只读的，需要创建一个内存流实例，用来获取响应流内容</span>
    <span class="token keyword">using</span> <span class="token punctuation">(</span><span class="token keyword">var</span> memStream <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MemoryStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token comment" spellcheck="true">//把内存流的引用设置到Response.Body，假装是真的响应体接收数据</span>
        context<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>Body <span class="token operator">=</span> memStream<span class="token punctuation">;</span>
        <span class="token comment" spellcheck="true">//然后执行下一个中间件，等待有响应返回</span>
        <span class="token keyword">await</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token comment" spellcheck="true">//需要判断响应是否正确，总不能把不正确的内容缓存起来吧</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>context<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>StatusCode <span class="token operator">==</span> <span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span>HttpStatusCode<span class="token punctuation">.</span>OK<span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            <span class="token comment" spellcheck="true">//检测文件存放目录</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>Directory<span class="token punctuation">.</span><span class="token function">Exists</span><span class="token punctuation">(</span>filePath<span class="token punctuation">)</span><span class="token punctuation">)</span>
                Directory<span class="token punctuation">.</span><span class="token function">CreateDirectory</span><span class="token punctuation">(</span>filePath<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token comment" spellcheck="true">//把内存流的当前操作位置设为0，因为响应写入的过程中位置会在末尾。</span>
            memStream<span class="token punctuation">.</span>Position <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
            <span class="token comment" spellcheck="true">//读取内存流的内容，转换为字符串</span>
            <span class="token keyword">var</span> responseBody <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StreamReader</span><span class="token punctuation">(</span>memStream<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ReadToEnd</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token comment" spellcheck="true">//把字符串写入文件，这里还稍微压缩了一下</span>
            <span class="token keyword">await</span> File<span class="token punctuation">.</span><span class="token function">WriteAllTextAsync</span><span class="token punctuation">(</span>fullPath<span class="token punctuation">,</span> Regex<span class="token punctuation">.</span><span class="token function">Replace</span><span class="token punctuation">(</span>responseBody<span class="token punctuation">,</span> <span class="token string">"\\n+\\s+"</span><span class="token punctuation">,</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token comment" spellcheck="true">//在此把内存流的当前操作位置设为0</span>
            memStream<span class="token punctuation">.</span>Position <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
            <span class="token comment" spellcheck="true">//还需要把流复制到之前引用的响应体实例</span>
            <span class="token keyword">await</span> memStream<span class="token punctuation">.</span><span class="token function">CopyToAsync</span><span class="token punctuation">(</span>originalBody<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">finally</span>
<span class="token punctuation">{</span>
    <span class="token comment" spellcheck="true">//把响应体实例引用重新设置到响应体</span>
    context<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>Body <span class="token operator">=</span> originalBody<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>好了，这样响应的内容终于可以缓存起来了，下次请求直接把静态文件输出就是了。但是，好像还有问题：</p>
<blockquote>
<ol>
<li>如何对特定的请求的url做缓存；</li>
<li>如何设置过期时间。</li>
</ol>
</blockquote>
<p>解决这两个问题的关键就在文件名上了，对于第一点很容易理解，直接把url作为文件名不就完了。没错，但是要注意特殊字符的问题，我这里就用md5处理一下了，这样相同的请求url就会返回相同的静态文件里的内容。</p>
<p>但是网站要更新，不能永久都是一样的数据呀，这时就需要设置缓存时间了。这里又会产生两个问题：</p>
<blockquote>
<ol>
<li>如何在文件上标识产生缓存时的时间；</li>
<li>如何判断时间是否超时。</li>
</ol>
</blockquote>
<p>针对这两个问题，我也考虑了很久，总是觉得直接在文件名上记录缓存时间和判断超时就好了,这也有两种方法：</p>
<blockquote>
<ol>
<li>读取文件名上的时间，与当前时间比对；</li>
<li>在超时时间内都能产生相同的文件名，判断是否存在这个文件。</li>
</ol>
</blockquote>
<p>从代码简洁的考虑上，我选择挑战难度大一点的第二个方法。那么如何使程序在超时时间内都产生相同的文件名呢？其实只需要实现一个算法，在一段时间内产生的时间对象都是相同的时间，忽略中间的时间变化。这个是不是很像小学数学求近似数里的去尾法?或者我们经常用到的<code>&quot;/&quot;</code>运算符，会吧小数点去掉。而在本例中，实现利用整除发忽略一段时间中的时间变化，生成同一个时间的算法和代码如下：</p>
<pre class="line-numbers language-csharp"><code class="language-csharp">       <span class="token keyword">var</span> timeTicks <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DateTime</span><span class="token punctuation">(</span>DateTime<span class="token punctuation">.</span>Now<span class="token punctuation">.</span>Ticks <span class="token operator">/</span> <span class="token number">10000000</span> <span class="token operator">/</span> expire <span class="token operator">*</span> <span class="token number">10000000</span> <span class="token operator">*</span> expire<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
<p>哈哈，只要一行代码,整除去掉多余的时间再乘回来构造一段时间内不变的时间。其实运行一下可以发现，如果超时时间设置成60秒，那么单位秒上的值会变成00。设置为3600秒，那么分秒两个单位都是0，因此会有一个弊端，真正缓存的时间很可能比设置的值短的，这个要看需求的容忍度啦！</p>
<p>就这样，问题一个个被解决，下面给出完整的中间件代码：</p>
<pre class="line-numbers language-csharp"><code class="language-csharp">    <span class="token keyword">var</span> hasExpire <span class="token operator">=</span> <span class="token keyword">int</span><span class="token punctuation">.</span><span class="token function">TryParse</span><span class="token punctuation">(</span>Configuration<span class="token punctuation">[</span><span class="token string">"html_cache_expire_time"</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token keyword">var</span> expire<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span>hasExpire <span class="token operator">&amp;&amp;</span> expire <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token comment" spellcheck="true">//文件缓存</span>
        app<span class="token punctuation">.</span><span class="token function">Use</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span>context<span class="token punctuation">,</span> next<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">></span>
        <span class="token punctuation">{</span>
            <span class="token keyword">var</span> url <span class="token operator">=</span> context<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>Path<span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">var</span> th <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MD5CryptoServiceProvider</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">var</span> data <span class="token operator">=</span> th<span class="token punctuation">.</span><span class="token function">ComputeHash</span><span class="token punctuation">(</span>Encoding<span class="token punctuation">.</span>Unicode<span class="token punctuation">.</span><span class="token function">GetBytes</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">var</span> key <span class="token operator">=</span> Convert<span class="token punctuation">.</span><span class="token function">ToBase64String</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> Base64FormattingOptions<span class="token punctuation">.</span>None<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">var</span> path <span class="token operator">=</span> HttpUtility<span class="token punctuation">.</span><span class="token function">UrlEncode</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token keyword">var</span> timeTicks <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DateTime</span><span class="token punctuation">(</span>DateTime<span class="token punctuation">.</span>Now<span class="token punctuation">.</span>Ticks <span class="token operator">/</span> <span class="token number">10000000</span> <span class="token operator">/</span> expire <span class="token operator">*</span> <span class="token number">10000000</span> <span class="token operator">*</span> expire<span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token keyword">const</span> <span class="token keyword">string</span> filePath <span class="token operator">=</span> <span class="token string">"static/cache/"</span><span class="token punctuation">;</span>
            <span class="token keyword">var</span> fileName <span class="token operator">=</span> path <span class="token operator">+</span> <span class="token string">"."</span> <span class="token operator">+</span> timeTicks<span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token string">"yyyyMMddHHmmss"</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">".html"</span><span class="token punctuation">;</span>
            <span class="token keyword">var</span> fullPath <span class="token operator">=</span> Path<span class="token punctuation">.</span><span class="token function">Combine</span><span class="token punctuation">(</span>filePath<span class="token punctuation">,</span> fileName<span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token keyword">if</span> <span class="token punctuation">(</span>File<span class="token punctuation">.</span><span class="token function">Exists</span><span class="token punctuation">(</span>fullPath<span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token punctuation">{</span>
                <span class="token keyword">await</span> context<span class="token punctuation">.</span>Response<span class="token punctuation">.</span><span class="token function">SendFileAsync</span><span class="token punctuation">(</span>fullPath<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
            <span class="token keyword">else</span>
            <span class="token punctuation">{</span>
                <span class="token keyword">var</span> originalBody <span class="token operator">=</span> context<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>Body<span class="token punctuation">;</span>

                <span class="token keyword">try</span>
                <span class="token punctuation">{</span>
                    <span class="token keyword">using</span> <span class="token punctuation">(</span><span class="token keyword">var</span> memStream <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MemoryStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
                    <span class="token punctuation">{</span>
                        context<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>Body <span class="token operator">=</span> memStream<span class="token punctuation">;</span>
                        <span class="token keyword">await</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                        <span class="token keyword">if</span> <span class="token punctuation">(</span>context<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>StatusCode <span class="token operator">==</span> <span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span>HttpStatusCode<span class="token punctuation">.</span>OK<span class="token punctuation">)</span>
                        <span class="token punctuation">{</span>
                            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>Directory<span class="token punctuation">.</span><span class="token function">Exists</span><span class="token punctuation">(</span>filePath<span class="token punctuation">)</span><span class="token punctuation">)</span>
                                Directory<span class="token punctuation">.</span><span class="token function">CreateDirectory</span><span class="token punctuation">(</span>filePath<span class="token punctuation">)</span><span class="token punctuation">;</span>

                            memStream<span class="token punctuation">.</span>Position <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
                            <span class="token keyword">var</span> responseBody <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StreamReader</span><span class="token punctuation">(</span>memStream<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ReadToEnd</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                            <span class="token keyword">await</span> File<span class="token punctuation">.</span><span class="token function">WriteAllTextAsync</span><span class="token punctuation">(</span>fullPath<span class="token punctuation">,</span> Regex<span class="token punctuation">.</span><span class="token function">Replace</span><span class="token punctuation">(</span>responseBody<span class="token punctuation">,</span> <span class="token string">"\\n+\\s+"</span><span class="token punctuation">,</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                            memStream<span class="token punctuation">.</span>Position <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
                            <span class="token keyword">await</span> memStream<span class="token punctuation">.</span><span class="token function">CopyToAsync</span><span class="token punctuation">(</span>originalBody<span class="token punctuation">)</span><span class="token punctuation">;</span>
                        <span class="token punctuation">}</span>
                    <span class="token punctuation">}</span>
                <span class="token punctuation">}</span>
                <span class="token keyword">finally</span>
                <span class="token punctuation">{</span>
                    context<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>Body <span class="token operator">=</span> originalBody<span class="token punctuation">;</span>
                <span class="token punctuation">}</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<h2 id="反思"><a href="#反思" class="headerlink" title="反思"></a>反思</h2><p>其实这个中间件还是有很多改进的地方，比如定义一套规则来给不同页面设置不同的超时时间，这样有的需要实时更新的内容就可以被区分开，而基本不变的内容就可以一直使用缓存。</p>

        
                <blockquote style="margin: 2em 0 0;padding: 0.5em 1em;border-left: 3px solid #F44336;background-color: #F5F5F5;list-style: none;">
                    <p><strong>
                         
                            <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/80x15.png" /></a><br />本作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议</a>进行许可。
                        </strong>
                        <br>
                        <strong>本文链接：</strong><a href="https://yangshunjie.com/A-Middleware-Implement-For-Server-Side-Static-Caching-In-AspNetCore.html">https://yangshunjie.com/A-Middleware-Implement-For-Server-Side-Static-Caching-In-AspNetCore.html</a>
                    </p>
                </blockquote>
        
    

    
</div>


                

                <!-- Post Comments -->
                
                    
    <!-- 使用 Gitalk -->
<div id="gitalk-comment">
    <!-- Gitalk 评论框 -->
<div id="gitalk-container"></div>

<link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css">

<script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>

<script>
    var gitalk = new Gitalk({
            clientID: '695234dcb3266b06c9bc',
            clientSecret: '9012a4415f4672e4858df564fc9999e9b6d5cec0',
            repo: 'elderjames.github.io',
            owner: 'ElderJames',
            admin: ['ElderJames'],
            // facebook-like distraction free mode
            distractionFreeMode: false
        })
   gitalk.render('gitalk-container')
</script>
</div>
<style>
    #gitalk-comment {
        background-color: #eee;
        padding: 2pc;
    }
</style>

                
            </div>

            <!-- Post Prev & Next Nav -->
            <nav class="material-nav mdl-color-text--grey-50 mdl-cell mdl-cell--12-col">
    <!-- Prev Nav -->
    
        <a href="/A-Middleware-Implement-For-Rendering-Razor-Views-In-AspNetCore.html" id="post_nav-newer" class="prev-content">
            <button class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--icon mdl-color--white mdl-color-text--grey-900" role="presentation">
                <i class="material-icons">arrow_back</i>
            </button>
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
            新篇
        </a>
    

    <!-- Section Spacer -->
    <div class="section-spacer"></div>

    <!-- Next Nav -->
    
        <a href="/influxdb-in-dotNEt-Core-2-ioc-and-repository-pattern.html" id="post_nav-older" class="next-content">
            旧篇
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
            <button class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--icon mdl-color--white mdl-color-text--grey-900" role="presentation">
                <i class="material-icons">arrow_forward</i>
            </button>
        </a>
    
</nav>

        </div>
    </div>



                    
                        <!-- Overlay For Active Sidebar -->
<div class="sidebar-overlay"></div>

<!-- Material sidebar -->
<aside id="sidebar" class="sidebar sidebar-colored sidebar-fixed-left" role="navigation">
    <div id="sidebar-main">
        <!-- Sidebar Header -->
        <div class="sidebar-header header-cover" style="background-image: url(/img/sidebar_header.png);">
    <!-- Top bar -->
    <div class="top-bar"></div>

    <!-- Sidebar toggle button -->
    <button type="button" class="sidebar-toggle mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--icon" style="display: initial;" data-upgraded=",MaterialButton,MaterialRipple">
        <i class="material-icons">clear_all</i>
        <span class="mdl-button__ripple-container">
            <span class="mdl-ripple">
            </span>
        </span>
    </button>

    <!-- Sidebar Avatar -->
    <div class="sidebar-image">
        <img src="https://avatars0.githubusercontent.com/u/7550366?v=4&amp;s=460" alt="James Yeung's avatar">
    </div>

    <!-- Sidebar Email -->
    <a data-toggle="dropdown" class="sidebar-brand" href="#settings-dropdown">
        shunjiey@hotmail.com
        <b class="caret"></b>
    </a>
</div>


        <!-- Sidebar Navigation  -->
        <ul class="nav sidebar-nav">
    <!-- User dropdown  -->
    <li class="dropdown">
        <ul id="settings-dropdown" class="dropdown-menu">
            
                <li>
                    <a href="mailto:shunjiey@hotmail.com" target="_blank" title="Email Me">
                        
                            <i class="material-icons sidebar-material-icons sidebar-indent-left1pc-element">email</i>
                        
                        Email Me
                    </a>
                </li>
            
        </ul>
    </li>

    <!-- Homepage -->
    
        <li id="sidebar-first-li">
            <a href="/">
                
                    <i class="material-icons sidebar-material-icons">home</i>
                
                主页
            </a>
        </li>
        
    

    <!-- Archives  -->
    
        <li class="dropdown">
            <a href="#" class="ripple-effect dropdown-toggle" data-toggle="dropdown">
                
                    <i class="material-icons sidebar-material-icons">inbox</i>
                
                    归档
                <b class="caret"></b>
            </a>
            <ul class="dropdown-menu">
            <li>
                <a class="sidebar_archives-link" href="/archives/2017/12/">十二月 2017<span class="sidebar_archives-count">1</span></a></li><li><a class="sidebar_archives-link" href="/archives/2017/11/">十一月 2017<span class="sidebar_archives-count">4</span></a></li><li><a class="sidebar_archives-link" href="/archives/2017/09/">九月 2017<span class="sidebar_archives-count">5</span></a></li><li><a class="sidebar_archives-link" href="/archives/2017/08/">八月 2017<span class="sidebar_archives-count">3</span></a></li><li><a class="sidebar_archives-link" href="/archives/2017/07/">七月 2017<span class="sidebar_archives-count">1</span></a></li><li><a class="sidebar_archives-link" href="/archives/2017/03/">三月 2017<span class="sidebar_archives-count">1</span></a></li><li><a class="sidebar_archives-link" href="/archives/2016/09/">九月 2016<span class="sidebar_archives-count">2</span></a></li><li><a class="sidebar_archives-link" href="/archives/2016/07/">七月 2016<span class="sidebar_archives-count">6</span></a></li><li><a class="sidebar_archives-link" href="/archives/2016/06/">六月 2016<span class="sidebar_archives-count">1</span></a></li><li><a class="sidebar_archives-link" href="/archives/2016/03/">三月 2016<span class="sidebar_archives-count">4</span></a></li><li><a class="sidebar_archives-link" href="/archives/2015/12/">十二月 2015<span class="sidebar_archives-count">1</span></a></li><li><a class="sidebar_archives-link" href="/archives/2015/11/">十一月 2015<span class="sidebar_archives-count">1</span></a></li><li><a class="sidebar_archives-link" href="/archives/2015/10/">十月 2015<span class="sidebar_archives-count">4</span></a>
            </ul>
        </li>
        
    

    <!-- Categories  -->
    
        <li class="dropdown">
            <a href="#" class="ripple-effect dropdown-toggle" data-toggle="dropdown">
                
                    <i class="material-icons sidebar-material-icons">chrome_reader_mode</i>
                
                分类
                <b class="caret"></b>
            </a>
            <ul class="dropdown-menu">
                <li>
                <a class="sidebar_archives-link" href="/categories/NET-Core/">.NET Core<span class="sidebar_archives-count">9</span></a></li><li><a class="sidebar_archives-link" href="/categories/DDD/">DDD<span class="sidebar_archives-count">1</span></a></li><li><a class="sidebar_archives-link" href="/categories/Docker/">Docker<span class="sidebar_archives-count">1</span></a></li><li><a class="sidebar_archives-link" href="/categories/Github-Pages/">Github Pages<span class="sidebar_archives-count">1</span></a></li><li><a class="sidebar_archives-link" href="/categories/Hexo/">Hexo<span class="sidebar_archives-count">2</span></a></li><li><a class="sidebar_archives-link" href="/categories/树莓派/">树莓派<span class="sidebar_archives-count">1</span></a>
            </ul>
        </li>
        
    

    <!-- Pages  -->
    
        <li>
            <a href="/tags.html" title="标签云">
                
                    <i class="material-icons sidebar-material-icons">local_offer</i>
                
                标签云
            </a>
        </li>
        
    
        <li>
            <a href="/timeline.html" title="时间轴">
                
                    <i class="material-icons sidebar-material-icons">list</i>
                
                时间轴
            </a>
        </li>
        
    
        <li>
            <a href="/contact.html" title="留言板">
                
                    <i class="material-icons sidebar-material-icons">forum</i>
                
                留言板
            </a>
        </li>
        
    
        <li>
            <a href="/about.html" title="关于我">
                
                    <i class="material-icons sidebar-material-icons">face</i>
                
                关于我
            </a>
        </li>
        
    
        <li>
            <a href="/gallery.html" title="相册">
                
                    <i class="material-icons sidebar-material-icons">image</i>
                
                相册
            </a>
        </li>
        
    
        <li>
            <a href="/friends.html" title="朋友">
                
                    <i class="material-icons sidebar-material-icons">people</i>
                
                朋友
            </a>
        </li>
        
    

    <!-- Article Number  -->
    
        <li>
            <a href="/archives">
                文章总数
                <span class="sidebar-badge">34</span>
            </a>
        </li>
        
    
</ul>


        <!-- Sidebar Footer -->
        <!--
I'm glad you use this theme, the development is no so easy, I hope you can keep the copyright, I will thank you so much.
If you still want to delete the copyrights, could you still retain the first one? Which namely "Theme Material"
It will not impact the appearance and can give developers a lot of support :)

很高兴您使用并喜欢该主题，开发不易 十分谢谢与希望您可以保留一下版权声明。
如果您仍然想删除的话 能否只保留第一项呢？即 "Theme Material"
它不会影响美观并可以给开发者很大的支持和动力。 :)
-->

<!-- Sidebar Divider -->


<!-- Theme Material -->


<!-- Help & Support -->
<!--

-->

<!-- Feedback -->
<!--

-->

<!-- About Theme -->
<!--

-->

    </div>

    <!-- Sidebar Image -->
    
    <span id="footer-image">
        <a href="https://ci.appveyor.com/project/ElderJames/elderjames-github-io" target="_blank" title="AppVeyor">
            <img src="https://ci.appveyor.com/api/projects/status/b0wack7uxrvifijj?svg=true" alt="AppVeyor"><!--
     --></a>
    </span>


</aside>

                    

                    
                        <!-- Footer Top Button -->
                        <div id="back-to-top" class="toTop-wrap">
    <a href="#top" class="toTop">
        <i class="material-icons footer_top-i">expand_less</i>
    </a>
</div>

                    

                    <!--Footer-->
<footer class="mdl-mini-footer" id="bottom" >
    
        <!-- Paradox Footer Left Section -->
        <div class="mdl-mini-footer--left-section sns-list">
    <!-- Twitter -->
    

    <!-- Facebook -->
    

    <!-- Google + -->
    

    <!-- Weibo -->
    

    <!-- Instagram -->
    

    <!-- Tumblr -->
    

    <!-- Github -->
    
        <a href="ElderJames" target="_blank">
            <button class="mdl-mini-footer--social-btn social-btn footer-sns-github">
                <span class="visuallyhidden">Github</span>
            </button><!--
     --></a>
    

    <!-- LinkedIn -->
    
        <a href="杨舜杰" target="_blank">
            <button class="mdl-mini-footer--social-btn social-btn footer-sns-linkedin">
                <span class="visuallyhidden">LinkedIn</span>
            </button><!--
     --></a>
    

    <!-- Zhihu -->
    
        <a href="ElderJames" target="_blank">
            <button class="mdl-mini-footer--social-btn social-btn footer-sns-zhihu">
                <span class="visuallyhidden">Zhihu</span>
            </button><!--
     --></a>
    

    <!-- Bilibili -->
    

    <!-- Telegram -->
    
    
    <!-- V2EX -->
    
</div>


        <!--Copyright-->
        <div id="copyright">
            Copyright&nbsp;©&nbsp;2015&nbsp;-<script type="text/javascript">var fd = new Date();document.write("&nbsp;" + fd.getFullYear() + "&nbsp;");</script>ElderJames' Blog
            
                <br>
                
                    学习/创作/分享<br /><a href="http://icp.chinaz.com/info?q=yangshunjie.com" rel="nofollow">粤ICP备15021181号-1</a>
                
            
        </div>

        <!-- Paradox Footer Right Section -->

        <!--
        I am glad you use this theme, the development is no so easy, I hope you can keep the copyright.
        It will not impact the appearance and can give developers a lot of support :)

        很高兴您使用该主题，开发不易，希望您可以保留一下版权声明。
        它不会影响美观并可以给开发者很大的支持。 :)
        -->

        <div class="mdl-mini-footer--right-section">
            <div>
                <div class="footer-develop-div">Powered by <a href="https://hexo.io" target="_blank" class="footer-develop-a">Hexo</a></div>
                <div class="footer-develop-div">Theme - <a href="https://github.com/viosey/hexo-theme-material" target="_blank" class="footer-develop-a">Material</a></div>
            </div>
        </div>
    
</footer>


                    <!-- Import JS File -->

    <script>lsloader.load("lazyload_js","/js/lazyload.min.js?1BcfzuNXqV+ntF6gq+5X3Q==", true)</script>



    <script>lsloader.load("js_js","/js/js.min.js?V/53wGualMuiPM3xoetD5Q==", true)</script>



    <script>lsloader.load("np_js","//cdn.bootcss.com/nprogress/0.2.0/nprogress.min.js", true)</script>


<script type="text/ls-javascript" id="NProgress-script">
    NProgress.configure({
        showSpinner: true
    });
    NProgress.start();
    $('#nprogress .bar').css({
        'background': '#29d'
    });
    $('#nprogress .peg').css({
        'box-shadow': '0 0 10px #29d, 0 0 15px #29d'
    });
    $('#nprogress .spinner-icon').css({
        'border-top-color': '#29d',
        'border-left-color': '#29d'
    });
    setTimeout(function() {
        NProgress.done();
        $('.fade').removeClass('out');
    }, 800);
</script>




    
        <script>lsloader.load("sm_js","/js/smoothscroll.js?lOy/ACj5suSNi7ZVFVbpFQ==", true)</script>
    





    <!-- Busuanzi -->
    <script src="https://dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js"></script>



<!-- CommentBox -->
   <!-- GitTalk -->





<!-- UC Browser Compatible -->
<script>
	var agent = navigator.userAgent.toLowerCase();
	if(agent.indexOf('ucbrowser')>0) {
		document.write('<link rel="stylesheet" href="/css/uc.css">');
	   alert('由于 UC 浏览器使用极旧的内核，而本网站使用了一些新的特性。\n为了您能更好的浏览，推荐使用 Chrome 或 Firefox 浏览器。');
	}
</script>

<!-- Import prettify js  -->

    
        
            <script>lsloader.load("prettify_js","//cdn.bootcss.com/prettify/r298/prettify.min.js", true)</script>
        
    



<!-- Window Load -->
<!-- add class for prettify -->
<script type="text/ls-javascript" id="window-load">
    $(window).on('load', function() {
        // Post_Toc parent position fixed
        $('.post-toc-wrap').parent('.mdl-menu__container').css('position', 'fixed');
    });

    
        
            $(function() {
                $('pre').addClass('prettyprint linenums').attr('style', 'overflow:auto;');
                prettyPrint();
                })
        
    
    
</script>

<!-- MathJax Load-->


<!-- Bing Background -->

<script type="text/ls-javascript" id="Bing-Background-script">
    queue.offer(function(){
        $('body').attr('data-original', 'https://api.i-meto.com/bing?type=S');
    });
</script>


    <script type = "text/ls-javascript" id= "title-script">
        //网页当前状态判断
        var hidden, state, visibilityChange,timer;
        var leaveTitle='(●-●) 我奔溃啦~';
        var stayTitle='(*´∇｀*) 被发现啦~';
        var difaultTitle = document.title;
        var resetTime=1000
        
                leaveTitle += document.title;
                stayTitle += document.title;
        

        if (typeof document.hidden !== "undefined") {
            hidden = "hidden";
            visibilityChange = "visibilitychange";
            state = "visibilityState";
        } else if (typeof document.mozHidden !== "undefined") {
            hidden = "mozHidden";
            visibilityChange = "mozvisibilitychange";
            state = "mozVisibilityState";
        } else if (typeof document.msHidden !== "undefined") {
            hidden = "msHidden";
            visibilityChange = "msvisibilitychange";
            state = "msVisibilityState";
        } else if (typeof document.webkitHidden !== "undefined") {
            hidden = "webkitHidden";
            visibilityChange = "webkitvisibilitychange";
            state = "webkitVisibilityState";
        }
        // 添加监听器，在title里显示状态变化
        document.addEventListener(visibilityChange, function () {
           // document.title = document[state] =='visible'?stayTitle:leaveTitle ;
            if (timer!=null) clearTimeout(timer);
            if (document[state] =='visible'){
                document.title = stayTitle;
                timer=setTimeout(function(){
                    document.title=difaultTitle;
                },resetTime)
            }
            else{
                document.title = leaveTitle;
            }
        }, false);
        //初始化页面状态
        document.title = document[state] =='visible'?stayTitle:leaveTitle ;
    </script>
    

<script type="text/ls-javascript" id="lazy-load">
    // Offer LazyLoad
    queue.offer(function(){
        $('.lazy').lazyload({
            effect : 'show'
        });
    });

    // Start Queue
    $(document).ready(function(){
        setInterval(function(){
            queue.execNext();
        },200);
    });
</script>

<!-- Custom Footer -->



<script>
    (function(){
        var scriptList = document.querySelectorAll('script[type="text/ls-javascript"]')

        for (var i = 0; i < scriptList.length; ++i) {
            var item = scriptList[i];
            lsloader.runInlineScript(item.id,item.id);
        }
    })()
console.log('\n %c © Material Theme | Version: 1.5.0 | https://github.com/viosey/hexo-theme-material %c \n', 'color:#455a64;background:#e0e0e0;padding:5px 0;border-top-left-radius:5px;border-bottom-left-radius:5px;', 'color:#455a64;background:#e0e0e0;padding:5px 0;border-top-right-radius:5px;border-bottom-right-radius:5px;');
</script>

                </main>
            </div>
            
                
<div id="hexo-helper-live2d">
  <canvas id="live2dcanvas" width="150" height="300"></canvas>
</div>
<style>
  #live2dcanvas{
    position: fixed;
    width: 150px;
    height: 300px;
    opacity:0.7;
    right: 0px;
    z-index: 999;
    pointer-events: none;
    bottom: 60px;
  }
</style>
<script type="text/javascript" src="/live2d/device.min.js"></script>
<script type="text/javascript">
const loadScript = function loadScript(c,b){var a=document.createElement("script");a.type="text/javascript";"undefined"!=typeof b&&(a.readyState?a.onreadystatechange=function(){if("loaded"==a.readyState||"complete"==a.readyState)a.onreadystatechange=null,b()}:a.onload=function(){b()});a.src=c;document.body.appendChild(a)};
(function(){
  if((typeof(device) != 'undefined') && (device.mobile())){
    document.getElementById("live2dcanvas").style.width = '75px';
    document.getElementById("live2dcanvas").style.height = '150px';
  }else
    if (typeof(device) === 'undefined') console.error('Cannot find current-device script.');
  loadScript("/live2d/script.js", function(){loadlive2d("live2dcanvas", "/live2d/assets/tororo.model.json", 0.5);});
})();
</script>

            
        </body>
    
</html>
